Executive Summary

This report analyzes temperature trends across Egyptian governorates from 1960 to 2024 using ERA5 reanalysis data. Key findings include:

  • Warming Trend: All governorates show clear warming trends since 1960
  • Recent Acceleration: Temperature deviations from the 1960-2010 baseline have intensified since 2010
  • Regional Variation: Temperature changes vary across governorates, with population-weighted measures often differing from unweighted spatial averages

Data and Methods

Data Sources

  • Climate Data: ERA5 reanalysis (1960-2024) at daily resolution
  • Population Data: LandScan population distribution (2020)
  • Baseline Period: 1960-2010 (used for calculating deviations and z-scores)

Weighting Methods

  1. Unweighted: Simple spatial average across each governorate
  2. Population-weighted: Weighted by where people actually live within each governorate

Population weighting provides a more accurate representation of human exposure to temperature changes.

Load Packages and Data

libs <- c("tidyverse", "here", "lubridate", "plotly", "scales", "knitr", "DT")

installed_libs <- libs %in% rownames(installed.packages())
if (any(installed_libs == FALSE)) {
  pak::pkg_install(libs[!installed_libs])
}

invisible(lapply(libs, library, character.only = TRUE))
# Load temperature data
era5_temp_df <- readRDS(file = paste(here(), "Data", "intermediate",
                                     "Governorate Data",
                                     "era5_temp_19602024.Rds", sep = "/"))

# Create annual data
era5_temp_annual <- era5_temp_df |>
  group_by(name, year) |>
  summarise(
    # Unweighted means
    annual_mean_temp = mean(mean_temp, na.rm = TRUE),
    annual_mean_maxtemp = mean(mean_maxtemp, na.rm = TRUE),
    annual_mean_mintemp = mean(mean_mintemp, na.rm = TRUE),
    # Population-weighted means
    annual_mean_temp_pop = mean(mean_temp_pop_weighted, na.rm = TRUE),
    annual_mean_maxtemp_pop = mean(mean_maxtemp_pop_weighted, na.rm = TRUE),
    annual_mean_mintemp_pop = mean(mean_mintemp_pop_weighted, na.rm = TRUE),
    .groups = "drop"
  )

# Calculate baseline statistics (1960-2010)
baseline_stats <- era5_temp_annual |>
  filter(year <= 2010) |>
  group_by(name) |>
  summarise(
    # Unweighted baselines
    mean_temp_baseline = mean(annual_mean_temp, na.rm = TRUE),
    sd_temp_baseline = sd(annual_mean_temp, na.rm = TRUE),
    mean_maxtemp_baseline = mean(annual_mean_maxtemp, na.rm = TRUE),
    sd_maxtemp_baseline = sd(annual_mean_maxtemp, na.rm = TRUE),
    mean_mintemp_baseline = mean(annual_mean_mintemp, na.rm = TRUE),
    sd_mintemp_baseline = sd(annual_mean_mintemp, na.rm = TRUE),
    # Population-weighted baselines
    mean_temp_pop_baseline = mean(annual_mean_temp_pop, na.rm = TRUE),
    sd_temp_pop_baseline = sd(annual_mean_temp_pop, na.rm = TRUE),
    mean_maxtemp_pop_baseline = mean(annual_mean_maxtemp_pop, na.rm = TRUE),
    sd_maxtemp_pop_baseline = sd(annual_mean_maxtemp_pop, na.rm = TRUE),
    mean_mintemp_pop_baseline = mean(annual_mean_mintemp_pop, na.rm = TRUE),
    sd_mintemp_pop_baseline = sd(annual_mean_mintemp_pop, na.rm = TRUE),
    .groups = "drop"
  )

# Join and calculate deviations
era5_temp_annual <- era5_temp_annual |>
  left_join(baseline_stats, by = "name") |>
  mutate(
    # Unweighted deviations
    temp_diff_mean = annual_mean_temp - mean_temp_baseline,
    temp_diff_max = annual_mean_maxtemp - mean_maxtemp_baseline,
    temp_diff_min = annual_mean_mintemp - mean_mintemp_baseline,
    z_score_mean = (annual_mean_temp - mean_temp_baseline) / sd_temp_baseline,
    z_score_max = (annual_mean_maxtemp - mean_maxtemp_baseline) / sd_maxtemp_baseline,
    z_score_min = (annual_mean_mintemp - mean_mintemp_baseline) / sd_mintemp_baseline,
    # Population-weighted deviations
    temp_diff_mean_pop = annual_mean_temp_pop - mean_temp_pop_baseline,
    temp_diff_max_pop = annual_mean_maxtemp_pop - mean_maxtemp_pop_baseline,
    temp_diff_min_pop = annual_mean_mintemp_pop - mean_mintemp_pop_baseline,
    z_score_mean_pop = (annual_mean_temp_pop - mean_temp_pop_baseline) / sd_temp_pop_baseline,
    z_score_max_pop = (annual_mean_maxtemp_pop - mean_maxtemp_pop_baseline) / sd_maxtemp_pop_baseline,
    z_score_min_pop = (annual_mean_mintemp_pop - mean_mintemp_pop_baseline) / sd_mintemp_pop_baseline
  )

Statistical Summary

Overall Temperature Statistics by Governorate

summary_stats <- era5_temp_annual |>
  filter(name %in% key_govs) |>
  group_by(name) |>
  summarise(
    `Mean (°C)` = round(mean(annual_mean_temp_pop, na.rm = TRUE), 2),
    `Max (°C)` = round(mean(annual_mean_maxtemp_pop, na.rm = TRUE), 2),
    `Min (°C)` = round(mean(annual_mean_mintemp_pop, na.rm = TRUE), 2),
    `Warming Trend (°C)` = round(
      coef(lm(annual_mean_temp_pop ~ year))[2] * (max(year) - min(year)), 2
    ),
    `Recent Decade Mean (2015-2024)` = round(
      mean(annual_mean_temp_pop[year >= 2015], na.rm = TRUE), 2
    ),
    .groups = "drop"
  ) |>
  rename(Governorate = name)

datatable(
  summary_stats,
  options = list(pageLength = 10, dom = 't'),
  caption = "Temperature statistics for the full period (1960-2024). Warming trend shows total temperature increase over the period."
)

Decadal Averages (Population-Weighted)

decade_stats <- era5_temp_annual |>
  filter(name %in% key_govs) |>
  mutate(decade = paste0(floor(year / 10) * 10, "s")) |>
  group_by(name, decade) |>
  summarise(
    `Mean Temp (°C)` = round(mean(annual_mean_temp_pop, na.rm = TRUE), 2),
    `Deviation (°C)` = round(mean(temp_diff_mean_pop, na.rm = TRUE), 2),
    .groups = "drop"
  ) |>
  pivot_wider(
    names_from = decade,
    values_from = c(`Mean Temp (°C)`, `Deviation (°C)`),
    names_glue = "{decade}_{.value}"
  ) |>
  rename(Governorate = name)

datatable(
  decade_stats,
  options = list(pageLength = 10, dom = 't', scrollX = TRUE),
  caption = "Decadal temperature averages showing progression of warming. Deviation is relative to 1960-2010 baseline."
)

Climate Shocks: Extreme Years

Years where temperature exceeded ±2 standard deviations from the baseline are considered extreme events or “climate shocks”.

shock_years <- era5_temp_annual |>
  filter(name %in% key_govs) |>
  filter(abs(z_score_mean_pop) > 2) |>
  mutate(
    `Shock Type` = ifelse(z_score_mean_pop > 2, "Heat", "Cold"),
    `Z-Score` = round(z_score_mean_pop, 2),
    `Deviation (°C)` = round(temp_diff_mean_pop, 2)
  ) |>
  select(Governorate = name, Year = year, `Shock Type`, `Z-Score`, `Deviation (°C)`) |>
  arrange(Governorate, desc(Year))

datatable(
  shock_years,
  options = list(pageLength = 10, scrollX = TRUE),
  caption = "Extreme temperature years (±2 SD from baseline). Recent years show more frequent heat shocks."
)

Key Findings

1. Accelerating Warming Trend

  • All governorates show clear warming trends since 1960
  • Total warming ranges from approximately 1.5°C to 2.5°C depending on location
  • The warming trend has accelerated since 2000, with the warmest years concentrated in the 2010s and 2020s

2. Regional Variations

  • Cairo and Alexandria (Lower Egypt): Show strong warming with significant heat stress implications for dense urban populations
  • Assiut and Suhag (Upper Egypt): Experience higher absolute temperatures and larger temperature swings

3. Population Weighting Matters

  • Population-weighted temperatures often differ from unweighted averages by 0.2-0.5°C
  • These differences reflect spatial variation in population distribution and climate within governorates
  • Population-weighted measures are more relevant for assessing human exposure to climate change

4. Increasing Frequency of Extreme Heat

  • Since 2010, multiple years have exceeded 2 standard deviations above the historical baseline
  • Cold extremes have become virtually absent in recent decades
  • This asymmetry indicates a fundamental shift in the temperature distribution

Methodology Notes

Z-Scores and Climate Shocks

Z-scores normalize temperature deviations by historical variability:

\[Z = \frac{T_{year} - \mu_{baseline}}{\sigma_{baseline}}\]

Where: - \(T_{year}\) is the annual temperature for a given year - \(\mu_{baseline}\) is the mean temperature for 1960-2010 - \(\sigma_{baseline}\) is the standard deviation for 1960-2010

Z-scores beyond ±2 indicate temperatures more than 2 standard deviations from the historical norm, representing exceptional events.

Population Weighting

Population-weighted temperatures are calculated by weighting each grid cell’s temperature by its population:

\[T_{weighted} = \frac{\sum_i T_i \times Pop_i}{\sum_i Pop_i}\]

This approach better represents human exposure to temperature changes than simple spatial averages.

Data Availability

All climate data and analysis code are available in the project repository:

  • Climate Data: Data/intermediate/Governorate Data/era5_temp_19602024.Rds
  • Analysis Code: Code/4_EDA/ClimateChange_Report.Rmd
  • Interactive App: Code/4_EDA/ClimateChange_ShinyApp.R

Report generated on 2026-01-20 using R version 4.5.2